require 'enumerator'

module WhoCharted
  class ChartBuilder
    ARGUMENT_NOT_PROVIDED = 'No %s argument was provided.'
    BASE_URL = 'http://chart.apis.google.com/chart?cht=%s&chs=%s&chd=%s'
    DEFAULT_ARGUMENTS = {:size => '600x300'}
    ACCEPTED_ARGUMENTS = [:size, :data, :value_method] # :type
    REQUIRED_ARGUMENTS = [:chart_type, :data, :value_method]
    OPTIONAL_ARGUMENTS = ACCEPTED_ARGUMENTS - REQUIRED_ARGUMENTS - DEFAULT_ARGUMENTS.keys

    def initialize args = {}
      @args = process_args args
    end

    def get_type_class
      cls = WhoCharted.const_get(chart_type_as_class_name)
    end

    def gather_data
      't:' + (data[0].class == Array ? \
              gather_data_from_array : \
              chart.data_values.join(','))
    end

    def build_base_url
      url = BASE_URL % [chart.chart_type, size, gather_data]
    end

    def chart
      @chart ||= get_type_class.new @args
    end

    def build
      build_base_url + chart.build_params
    end

    attr_reader :chart_type, *ACCEPTED_ARGUMENTS

    private

    attr_writer :chart_type, *ACCEPTED_ARGUMENTS

    def set_self_value key, value
      send((key.to_s + '='), value)
    end

    def chart_type_as_class_name
      /([^_]+)_?/.match(chart_type.to_s).captures.map {|s| s.capitalize}.join('') + 'Chart'
    end

    def apply_args args
      args.each do |key, value|
        case key
        when :type
          @chart_type = value
          true
        when *ACCEPTED_ARGUMENTS
          set_self_value(key, value)
          true
        else false
        end
      end
    end

    def set_default_args args
      DEFAULT_ARGUMENTS.each {|k,v| set_self_value(k,v) if !args.has_key?(k)}
    end

    def validate_args
      REQUIRED_ARGUMENTS.each do |arg|
        raise ArgumentError.new(ARGUMENT_NOT_PROVIDED % arg.inspect) \
          if send(arg).nil?
      end
    end

    def process_args args
      apply_args args
      set_default_args args
      validate_args
      args
    end

    def gather_data_from_array
      chart.data_values.map {|a| a.join(',')}.join('|')
    end
  end
end
